﻿using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System;
using XamlaApp.ViewModels;
using XamlaApp.Interfaces;
using XamlaApp.Helpers;
using System.Linq;

namespace XamlaApp.Views
{
    public class Stage : Canvas
    {
        private readonly Dictionary<FrameworkElement, IXamlaElement> _dragElements =
            new Dictionary<FrameworkElement, IXamlaElement>();

        private FrameworkElement _capturedElement; //used during drag handling
        private bool _minimizedDrag;
        private bool _mouseCaptured;

        private Point _offSet;

        public event EventHandler ElementsChanged;
        private void OnElementsChanged()
        {
            if (ElementsChanged != null) ElementsChanged(this, new EventArgs());
        }
        public Stage()
        {
            SizeChanged += (sender, args) =>
                               {
                                   //Update the clip
                                   var r = new RectangleGeometry
                                               {
                                                   Rect = new Rect(0, 0, args.NewSize.Width, args.NewSize.Height)
                                               };
                                   Clip = r;
                               };
            Loaded += Stage_Loaded;
        }

        #region Element Z-Ordering
        //NOTE: (prujohn) I'm not really happy with this Z-Ording technique.  It works, but...
        //Feels rather draconian to have to manually move elements around in the Children collection.
        //I tried some techniques with Canvas.SetZIndex, but results were not good enough to meet
        //user expectations...
        private void ValidateElement(UIElement element)
        {
            if (element == null) throw new ArgumentNullException("element");
            if (!Children.Contains(element)) throw new ArgumentException("element");
        }

        public void BringForward(UIElement element)
        {
            ValidateElement(element);

            var mvm = Container.AppContainer.Resolve<MainUIViewModel>();
            mvm.RemoveControlEditor();
            var i = Children.IndexOf(element);
            if (i < Children.Count-1)
            {
                Children.Remove(element);
                Children.Insert(i + 1, element);
            }
            mvm.DisplayControlEditor(((IXamlaElement)mvm.CurrentElement).ViewModel);
            OnElementsChanged();
        }

        public void SendBackward(UIElement element)
        {
            ValidateElement(element);

            var mvm = Container.AppContainer.Resolve<MainUIViewModel>();
            mvm.RemoveControlEditor();
            var i = Children.IndexOf(element);
            if (i > 0)
            {
                Children.Remove(element);
                Children.Insert(i - 1, element);
            }
            mvm.DisplayControlEditor(((IXamlaElement)mvm.CurrentElement).ViewModel);
            OnElementsChanged();
        }

        public void SendToBack(UIElement element)
        {
            ValidateElement(element);

            var mvm = Container.AppContainer.Resolve<MainUIViewModel>();
            mvm.RemoveControlEditor();
            var i = Children.IndexOf(element);
            if (i > 0)
            {
                Children.Remove(element);
                Children.Insert(0, element);
            }
            mvm.DisplayControlEditor(((IXamlaElement)mvm.CurrentElement).ViewModel);
            OnElementsChanged();
        }

        public void BringToFront(UIElement element)
        {
            ValidateElement(element);

            var mvm = Container.AppContainer.Resolve<MainUIViewModel>();
            mvm.RemoveControlEditor();
            var i = Children.IndexOf(element);
            if (i < Children.Count-1)
            {
                Children.Remove(element);
                Children.Add(element);
            }
            mvm.DisplayControlEditor(((IXamlaElement)mvm.CurrentElement).ViewModel);
            OnElementsChanged();
        }

        #endregion

        public void AddElement(UserControl element)
        {
            if (!Children.Contains(element))
                Children.Add(element);
            else 
                throw new ArgumentException("element already member of Stage collection");
            OnElementsChanged();
        }

        internal void DeleteElement(UserControl element)
        {
            if (Children.Contains(element))
            {
                UnRegisterDraggableElement(element);
                Children.Remove(element);
                OnElementsChanged();
            }
            else
            {
                throw new ArgumentException("element not found in Stage collection");
            }
        }

        void Stage_Loaded(object sender, RoutedEventArgs e)
        {
            //trying to do this in the construtor leads to stack overflow due to circular references
            Container.AppContainer.Resolve<XamlaMainPage>().MouseMove += Canvas_MouseMove;
        }

        public void RegisterDraggableElement(IXamlaElement elementToRegister)
        {
            if (elementToRegister == null || elementToRegister == default(UIElement)) throw new ArgumentException("elementToRegister");

            //Add a drag event if this element is marked as draggable
            if (elementToRegister.GetType().ImplementsInterface(typeof(IDraggable)))
            {
                ((IDraggable)elementToRegister).DraggableElement.MouseLeftButtonDown += DraggableElement_MouseLeftButtonDown;
                _dragElements.Add(((IDraggable)elementToRegister).DraggableElement, elementToRegister);
            }
        }
        public void RegisterDraggableElement(IXamlaElement elementToRegister, FrameworkElement dragHandle)
        {
            if (elementToRegister == null || elementToRegister == default(UIElement)) throw new ArgumentException("elementToRegister");

            if (!_dragElements.ContainsKey(dragHandle))
            {
                if (Children.Contains((FrameworkElement)elementToRegister) && elementToRegister.GetType().ImplementsInterface(typeof(IDraggable)))
                {
                    dragHandle.MouseLeftButtonDown += DraggableElement_MouseLeftButtonDown;
                    _dragElements.Add(dragHandle, elementToRegister);
                }
            }
        }
        public void UnRegisterDraggableElement(UIElement elementToUnregister)
        {
            if (elementToUnregister == null || elementToUnregister == default(UIElement)) throw new ArgumentException("elementToRegister");

            //Remove a drag event if this element is marked as draggable
            if (elementToUnregister.GetType().ImplementsInterface(typeof(IDraggable)))
            {
                ((IDraggable)elementToUnregister).DraggableElement.MouseLeftButtonDown -= DraggableElement_MouseLeftButtonDown;
                _dragElements.Remove(((IDraggable)elementToUnregister).DraggableElement);
            }
        }

        /* Drag and Drop Facility */

        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            var mousePos = e.GetPosition(null);
            //If an element is being dragged..
            if (_mouseCaptured)
            {
                //..Drag the element to the current mouse position, less offset (using it's view model)
                _dragElements[_capturedElement].ViewModel.Left = mousePos.X - _offSet.X;
                _dragElements[_capturedElement].ViewModel.Top = mousePos.Y - _offSet.Y;
                ((IDraggable)_dragElements[_capturedElement]).OnDragging();
            }
        }

        private void DraggableElement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var uie = (FrameworkElement)sender;
            if (((IDraggable)_dragElements[uie]).DragEnabled)
            {
                ((IDraggable)_dragElements[uie]).OnDragStarted();
                {


                    _offSet = new Point(e.GetPosition(null).X - _dragElements[uie].ViewModel.Left,
                    e.GetPosition(null).Y - _dragElements[uie].ViewModel.Top);

                    //_offSet = new Point(e.GetPosition(this).X - ((IXamlaElement)(_dragElements[uie])).ViewModel.Left,
                    //e.GetPosition(this).Y - ((IXamlaElement)(_dragElements[uie])).ViewModel.Top);
                }

                _capturedElement = uie;
                _mouseCaptured = true;
                MouseLeftButtonUp += Handler_MouseLeftButtonUp;
                uie.MouseLeftButtonUp += Handler_MouseLeftButtonUp;
            }
            e.Handled = true;
        }

        private void Handler_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _mouseCaptured = false;
            try
            {
                ((IDraggable)_dragElements[_capturedElement]).OnDragEnded();
                if (!_minimizedDrag)
                {
                    _capturedElement.MouseLeftButtonUp -= Handler_MouseLeftButtonUp;
                }
                else
                {
                    _minimizedDrag = false;
                }
                MouseLeftButtonUp -= Handler_MouseLeftButtonUp;
                ((IDraggable)_dragElements[_capturedElement]).OnLocationChanged();
            }
            // ReSharper disable EmptyGeneralCatchClause
            catch { }
            // ReSharper restore EmptyGeneralCatchClause
            e.Handled = true;
        }

        internal void RemoveAllElements()
        {
            while (Children.Any())
            {
                DeleteElement((UserControl)Children.First());
            }
        }
    }

    /// <summary>
    /// A contract that allows elements in the myOSity UI to be dragged by the user.
    /// </summary>
    public interface IDraggable
    {
        FrameworkElement DraggableElement { get; }
        bool DragEnabled { get; }
        void OnLocationChanged();
        void OnDragging();
        void OnDragStarted();
        void OnDragEnded();
    }
}



